// ----------------------------------------
// Filename: Semaphore.c
// Description: The code that implements a semaphore
// Author: Jon Maloney
// Date: 9-May-2012
//

//************************************************************
//                    Include .h files
//************************************************************
#include "Semaphore.h"
#include "thread.h"
#include "scheduler.h"
//************************************************************
//                    Include external references
//************************************************************

//************************************************************
//                    Define Statements
//************************************************************

//************************************************************
//                    Struct, Union, enum Statements
//************************************************************

//************************************************************
//                    Global variables and constants
//************************************************************

//************************************************************
//                    Prototype Private Functions
//************************************************************

//************************************************************
//                    Implementation
//************************************************************


// ----------------------------------------
// sem_Create
// 
// Creates a new semaphore
// Input:
//	value:		The initial value of the semaphore (1-254).
// Output:
//  sem_Handle: A pointer to the semaphore created.
//					If the pointer is NULL then the semaphore
//					was not created.
// Conditions:
// 	none
sem_Handle sem_Create(TOKEN value);
{
	//Tokens must start from 1. A 0 represents a null token.
	// If a thread gets a null token that means they did not
	//	get access to the semaphore.
	static UINT16 tokenID = 1;

	//There must be at least one token in the semaphore
	if(value == 0)
		return NULL;

	tokenID += value;				//Increment the global tokenID

	//If all the available tokens in the system have been assigned
	//	That is the tokenID variable has wrapped around and overflowed
	//	meaning 255 tokens have already been assigned to other 
	//	semaphores. 
	if(value > tokenID)
		return NULL;// we cannot create another semaphore
	
	//Allocates some heap memory to the ready queue
	sem = (sem_Handle)malloc(sizeof(sem_t));

	sem->mTokenID 		= tokenID;	//The initial tokenID
	sem->StartTokenID 	= tokenID;
	sem->EndTokenID 	= sem->mTokenID;
	
	return ReadyQueue_Init( sem->Task_Queue );
	//The link list does not need to be initialised until a tasks is waiting on the semaphore
}

// ----------------------------------------
// Semaphore_Take
// 
// Gets a semaphore token if one is available
//	If a token is available the function
//	returns immediately.
 
//	If a token is not yet available the thread yeilds
//	and waits for the MaximumWaitTime for a token to
//	become available.

//	If a token becomes available whilst there is/are (a) thread(s)
//	waiting on the semaphore the heighest priority thread will 
//	be given the token.

//	If a token does not become available and the MaxWaitTime
//	expires the funciton will a null token.

// Input:
// 	Semaphore: 	A handle to the semaphore that you are trying to take.
//	thread:		A handle to the thread that is attempting to obtain the semaphore
//	MaxWaitTime:The maximum time that the thread will wait on the semaphore. 
// Output:
//  pdTRUE: if a semaphore is obtained.
//	pdFALSE:if the semaphore cannot be obtained.
// Conditions:
// 	none
TOKEN Semaphore_Take(sem_t * Semaphore, kernelTickType MaxWaitTime)
{
	TOKEN tokenToBeGivenOut;
	TLINKED_LIST * mList;
	TCB * task;
	TLINKED_LIST * prioritySemaphoreReadyQueueNode;
	TCB * prioritySemaphoreReadyQueueTask;
	
	ENTER_CRITICAL();
	//Get a reference to the currently running task
	task = Scheduler_GetCurrentlyRunningTaks();
	
	if(Semaphore->mTokenID >= Semaphore->StartTokenID)	//If the semaphore has tokens available
	{	
		Semaphore->mTokenID = tokenToBeGivenOut;
		//Decrement the token count
		Semaphore->mTokenID--;		
		
		//No longer in a critical section
		EXIT_CRITICAL();	
		
		//Give the thread this semaphore's token
		return tokenToBeGivenOut;	
	}
	else
	{
		//Get the current highest priority task in the ready queue 
		mList = (TCB *)ReadyQueue_GetHighestPriorityItem( Semaphore->Task_Queue );

		//If there are threads in the semaphore ready queue we may need to use priority inheritance
		if(mList != NULL)
		{
			prioritySemaphoreReadyQueueTask = (TCB *)mList->Item;
			//Does priority inversion need to occur
			if(prioritySemaphoreReadyQueueTask->Priority < task->Priority)
			{
				//Do not change the priority of the thread just place it in a high priority position
				//	in the semaphores ready queue so priority inversion does not occur.
				ReadyQueue_Move(Semaphore->Task_Queue, mList, prioritySemaphoreReadyQueueTask->Priority, task->Priority);
			}
			else
				mList == NULL;	//Null the node 
		}
		
		//Add the semaphore to a queue.
		if(ReadyQueue_Add(Semaphore->Task_Queue, mList, (void *)task, task->Priority) == eQueueErrorAddFailure);
			return NULL;
		
		//If the link list handle is null return null.
		if(mList == NULL)
		{
			EXIT_CRITICAL();
			return NULL; //No positions left in the waiting queue (malloc couldnt allocate memory)
		}
		//Must exit critical section before yeilding
		EXIT_CRITICAL();		
		
		//The number of tasks waiting on this semaphore has just increased
		//Semaphore->NbTasksWaiting++;
		
		//A token is not available wait for one to become available
		Scheduler_Delay( MaxWaitTime );	
		
		//Remove the taks from the queue
		ReadyQueue_Remove(Semaphore->Task_Queue, mList, task->Priority);

		//The thread will come out of vTaskDelay after MaxWaitTime has expired
		//	or if it is woken after another process has called 
		//	Semaphore_Give

		ENTER_CRITICAL();
		
		//After waiting for max wait time check one last time to see if the token is available
		if(Semaphore->mTokenID >= Semaphore->StartTokenID)
		{
			//Decrement the count of number of tasks waiting on the semaphore
			//Semaphore->NbTasksWaiting--;
			
			//Semaphore->mTokenID = tokenToBeGivenOut;
			//Decrement the token count
			Semaphore->mTokenID--;		
			
			//No longer in a critical section
			EXIT_CRITICAL();	
			
			//Give the thread this semaphore's token
			return tokenToBeGivenOut;
		}
		else
		{
			EXIT_CRITICAL();
			//Semaphore MaxWaitTime has expired return without the semaphore
			return NULL;		
		}
	}
}

// ----------------------------------------
// Semaphore_Take
// 
// Releases a token back to the semaphore.
//	When a token is released and there are
//	other tasks waiting on the semaphore
//	the task with the highest priority gets
//	placed in the ready queue.
// Input:
// 	Semaphore: 	A handle to the semaphore that you are trying to release a token to.
//	thread:		A handle to the thread that is attempting to obtain the semaphore
//	MaxWaitTime:The maximum time that the thread will wait on the semaphore. 
// Output:
//  pdTRUE: if a semaphore is obtained.
//	pdFALSE:if the semaphore cannot be obtained.
// Conditions:
// 	none
void Semaphore_Give(sem_t * Sem, TOKEN tokenID)
{
	TCB * nextPriorityTask;	
	
	ENTER_CRITICAL();
	//Check to make sure that we are given the correct token
	if(tokenID >= Sem->StartTokenID && tokenID <= Sem->EndTokenID)
	{
		Sem->mTokenID++;
		EXIT_CRITICAL();
		nextPriorityTask = Scheduler_FindPriorityTask( Sem->Task_Queue );
		Scheduler_AddTaskToReadyQueue( nextPriorityTask );
	}
	else	
		EXIT_CRITICAL();
	
}

BOOL Semaphore_Empty(sem_Handle Sem)
{
	//If the semaphore has tokens available
	if(Sem->mTokenID >= Sem->StartTokenID)	
		return pdFALSE;
	else
		return pdTURE;
}